﻿/*   
    Copyright (C) 2009 Galaktika Corporation ZAO

    This file is a part of Galaktika.BusinessMonitor
 
    Galaktika.BusinessMonitor is a free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
      
    You should have received a copy of the GNU General Public License
    along with Galaktika.BusinessMonitor.  If not, see <http://www.gnu.org/licenses/>.
  
    If GPL v.3 is not suitable for your products or company,
    Galaktika Corp provides Galaktika.BusinessMonitor under a flexible commercial license
    designed to meet your specific usage and distribution requirements.
    If you have already obtained a commercial license from Galaktika Corp,
    you can use this file under those license terms.
*/

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Net;
using System.Reflection;
using System.Security.Principal;
using System.Threading;
using System.Windows;
using System.Windows.Media;

namespace Galaktika.BI.Silverlight.ClientApp
{
    using Galaktika.BI.Silverlight.Runtime.Data;
    using Galaktika.BI.Silverlight.ClientApp.Data;
    using Galaktika.BI.Silverlight.ClientApp.Services;
    using Galaktika.BI.Extensibility;
    using Galaktika.BI.Extensibility.Dom;
    using Galaktika.BI.Extensibility.Dom.Dac;
    using Galaktika.BI.Extensibility.Dom.Workstation;
    using Galaktika.BI.Runtime;
    using Galaktika.BI.Runtime.Services;
    using Ranet.Olap.Core.Data;
    using System.Windows.Controls;
    using Galaktika.BI.Silverlight.Services;
    using Galaktika.BI.Silverlight.ClientApp.Web.Runtime.DataAccess;
    using Galaktika.BI.Silverlight.Commands;

    public class DataBinder
    {
        const string DEFAULT_CONTEXT_NAME = "";
        #region Utils
        private static class Utils
        {
            public static bool AreEquals<T>(T[] ar1, T[] ar2)
            {
                if (ar1 == null && ar2 == null) return false;
                if (ar1 == null || ar2 == null) return false;
                if (ar1.Length != ar2.Length) return false;

                for (int i = 0; i < ar1.Length; i++)
                {
                    if (!ar1[i].Equals((ar2[i]))) return false;
                }

                return true;
            }
        }
        #endregion
        #region DataBindingContext
        private class DataBindingContext : IBindingContext, IUniquedObject
        {
            public DataBindingContext(DataBinder binder, string id)
            {
                this.Binder = binder;
                this.Id = id;
                m_Managers = new Dictionary<IDataManager, IBindingManager>();
                this.IsDirty = true;
            }

            public string Id { get; private set; }
            public DataBinder Binder { get; private set; }
            public VariablesContainer Variables { get; set; }

            private Dictionary<IDataManager, IBindingManager> m_Managers;

            public void ResetOlapManagers()
            {
                foreach (IBindingManager manager in m_Managers.Values)
                {
                    if (manager.DataManager is OlapDataManager)
                    {
                        ((OlapDataManager)manager.DataManager).History.ClearHistory();
                    }
                }
            }

            public IEnumerable<IDataManager> DataManagers
            {
                get
                {
                    return m_Managers.Keys;
                }
            }

            #region IBindingContext Members

            public IServiceProvider Services
            {
                get
                {
                    return this.Binder.Services;
                }
            }

            public bool IsDirty { get; set; }

            public void Reset()
            {
                foreach (IBindingManager manager in m_Managers.Values)
                {
                    manager.Reset();
                }
                if (m_UserData != null)
                {
                    m_UserData.Clear();
                }
            }

            public void Refresh()
            {
                foreach (IBindingManager manager in m_Managers.Values)
                {
                    manager.Refresh();
                }
            }

            public IBindingManager this[IDataManager dataManager]
            {
                get
                {
                    IBindingManager manager;
                    m_Managers.TryGetValue(dataManager, out manager);
                    if (manager == null)
                    {
                        manager = new DataBindingManager(this.Binder, this, dataManager);
                        m_Managers.Add(dataManager, manager);
                    }

                    return manager;
                }
            }

            public IBindingManager this[string dataSourceId]
            {
                get
                {
                    foreach (var dataManager in m_Managers)
                    {
                        if (dataManager.Key.Name == dataSourceId)
                        {
                            return dataManager.Value;
                        }
                    }

                    return null;
                }
            }

            private SafeDictionary<string, object> m_UserData;
            public IDictionary<string, object> UserData
            {
                get
                {
                    if (m_UserData == null)
                    {
                        m_UserData = new SafeDictionary<string, object>();
                    }
                    if (!m_UserData.ContainsKey("_$Id"))
                    {
                        m_UserData.Add("_$Id", this.Id);
                    }

                    return m_UserData;
                }
            }

            #endregion
        }
        #endregion
        #region DataBindingManager
        private class DataBindingManager : IBindingManager
        {
            public DataBindingManager(DataBinder binder, IBindingContext context, IDataManager clientDataManager)
            {
                this.Binder = binder;
                m_Context = context;
                m_DataManager = clientDataManager;
            }

            public DataBinder Binder { get; private set; }

            #region IBindingManager Members

            private IBindingContext m_Context;
            public IBindingContext Context
            {
                get
                {
                    return m_Context;
                }
            }

            private IDataManager m_DataManager;
            public IDataManager DataManager
            {
                get
                {
                    return m_DataManager;
                }
            }

            private int m_Count = 0;
            public int Count
            {
                get
                {
                    return m_Count;
                }
            }

            private int[] m_Position = new int[] { 0 };
            public int[] Position
            {
                get
                {
                    return m_Position;
                }
                set
                {
                    if (!Utils.AreEquals<int>(m_Position, value))
                    {
                        m_Position = value;
                        m_Context.IsDirty = true;
                    }
                }
            }

            public void Reset()
            {
                //m_Context.Reset();
                m_BindObject = null;
                m_Position = new int[] { 0 };
            }

            public bool IsDataDirty
            {
                get
                {
                    return m_BindObject == null;
                }
            }

            //ManualResetEvent m_CreateObjEvent = new ManualResetEvent(false);

            //private class ResultContainer
            //{
            //    public ResultContainer()
            //    {
            //    }

            //    public object Content;
            //}

            DataSourcePathResolver m_Resolver;
            public void CreateBindObject()
            {
                m_BindObject = m_DataManager[this.Context];
                m_Resolver = null;
            }

            public void Refresh()
            {
                IAppManager app = App.CurrentApp.GetService<IAppManager>();
                IBindingContext context = App.CurrentApp.Binder.GetContext((FrameworkElement)app.CurrentForm);

                if (app.CurrentForm is FormView)
                    App.CurrentApp.Binder.Refresh(context);
                //if (m_Context.IsDirty)
                //{
                //    App.CurrentApp.Binder.Refresh(m_Context);
                //}
            }

            private object m_BindObject;
            internal object BindObject
            {
                get
                {
                    if (m_BindObject == null)
                    {
                        this.CreateBindObject();
                    }

                    return m_BindObject;
                }
                set
                {
                    m_BindObject = value;
                }
            }

            public object this[string path]
            {
                get
                {
                    object obj = this.DoDrillDown(this.BindObject, path);
                    //object obj = this.BindObject;
                    return obj;
                }
            }

            private object DoDrillDown(object dataSource, string path)
            {
                //if (string.IsNullOrEmpty(path))
                //{
                //    return dataSource;
                //}
                if (m_Resolver == null)
                {
                    m_Resolver = new DataSourcePathResolver();
                }
                var obj = m_Resolver.Resolve(dataSource, path, this.Position[0]);
                this.m_Count = m_Resolver.Count;
                if (obj != null)
                    return obj;
                else
                    return dataSource;
            }

            #endregion
        }

        private class DataBindingManagerHolder : IBindingManager
        {
            private IBindingManager m_BaseManager;
            public DataBindingManagerHolder(IBindingManager baseManager, string path)
            {
                m_BaseManager = baseManager;
                this.Path = path;
            }

            public string Path { get; private set; }

            public void PreloadData()
            {
                if (m_BaseManager[string.Empty] == null)
                {
                    m_BaseManager.CreateBindObject();
                }
            }

            #region IBindingManager Members

            public int Count
            {
                get { return m_BaseManager.Count; }
            }

            public int[] Position
            {
                get
                {
                    return m_BaseManager.Position;
                }
                set
                {
                    m_BaseManager.Position = value;
                }
            }

            public IDataManager DataManager
            {
                get { return m_BaseManager.DataManager; }
            }

            public IBindingContext Context
            {
                get { return m_BaseManager.Context; }
            }

            public void Reset()
            {
                m_BaseManager.Reset();
            }

            public void Refresh()
            {
                m_BaseManager.Refresh();
            }

            public object this[string path]
            {
                get { return m_BaseManager[path]; }
            }

            public bool IsDataDirty
            {
                get { return m_BaseManager.IsDataDirty; }
            }

            public void CreateBindObject()
            {
                m_BaseManager.CreateBindObject();
            }

            #endregion
        }
        #endregion
        #region DataSourceManager
        private class DataSourceManager
        {
            SafeDictionary<string, BrickDataSource> m_Cache;
            public DataSourceManager(IEnumerable<BrickDataSource> dataSources)
            {
                m_Cache = new SafeDictionary<string, BrickDataSource>();
                foreach (BrickDataSource ds in dataSources)
                {
                    if (ds.Query == null || ds.Connection == null) continue;
                    m_Cache.Add(ds.Name, ds);
                }
            }

            public string GetContentType(string dataSourceName)
            {
                BrickDataSource dataManager;
                m_Cache.TryGetValue(dataSourceName, out dataManager);
                if (dataManager != null)
                {
                    if (dataManager.Connection.Provider == DataProvider.Olap)
                    {
                        return KnownContentTypes.MultidimData;
                    }

                    return KnownContentTypes.RelationalData;
                }

                return KnownContentTypes.Error;
            }

            //public BrickD this[string dataSourceName]
            //{
            //    get
            //    {
            //        return m_Cache[dataSourceName];
            //    }
            //}

            public IDataManager CreateDataManager(string dataSourceName)
            {
                BrickDataSource ds;
                m_Cache.TryGetValue(dataSourceName, out ds);
                if (ds != null)
                {
                    BrickConnection conn = ds.Connection;
                    BrickQuery query = ds.Query;
                    switch (conn.Provider)
                    {
                        case DataProvider.Odbc:
                            return new OdbcDataManager(ds.Name, conn.ConnectionString, query.Query);
                        case DataProvider.OleDb:
                            return new OleDbDataManager(ds.Name, conn.ConnectionString, query.Query);
                        case DataProvider.SqlServer:
                            return new SqlDataManager(ds.Name, conn.ConnectionString, query.Query);
                        case DataProvider.Olap:
                            return new OlapDataManager(ds.Name, new Ranet.Olap.Core.ConnectionInfo(conn.Name, conn.ConnectionString), query.Query, query.UpdateScript);
                    }
                }

                return null;
            }

            public SafeDictionary<string, BrickDataSource> AsDictionary
            {
                get { return m_Cache; }
            }


        }
        #endregion
        #region NamedObjectCache
        private class NamedObjectCache
        {
            private SafeDictionary<string, BrickObject> m_Cache;
            public NamedObjectCache(BrickDacContainer dacContainer)
            {
                m_Cache = new SafeDictionary<string, BrickObject>();
                foreach (BrickDataSource ds in dacContainer.DataSources)
                {
                    if (!m_Cache.ContainsKey(ds.Connection.Name))
                    {
                        m_Cache.Add(ds.Connection.Name, ds.Connection);
                    }
                    if (!m_Cache.ContainsKey(ds.Name))
                    {
                        m_Cache.Add(ds.Name, ds);
                    }
                }
            }

            public BrickObject this[string name]
            {
                get
                {
                    return m_Cache[name];
                }
            }
        }
        #endregion

        public DataBinder(IServiceProvider services)
        {
            this.UserSettings = new UserSettings();
            this.Services = services;
            m_Contexts = new SafeDictionary<string, IBindingContext>();
            m_Contexts.Add(DEFAULT_CONTEXT_NAME, new DataBindingContext(this, DEFAULT_CONTEXT_NAME));
        }

        private static IDictionary<string, IBindingContext> m_Contexts;
        public IBindingContext DefaultContext
        {
            get
            {
                return m_Contexts[DEFAULT_CONTEXT_NAME];
            }
        }

        public IBindingContext GetContext(string contextId)
        {
            return this.GetOrCreateContext(contextId);
        }

        public VariablesContainer DefaultVariables
        {
            get
            {
                return this.GetVariables(this.DefaultContext);
            }
        }

        internal VariablesContainer GetVariables(string contextId)
        {
            return this.GetVariables(this.GetContext(contextId));
        }

        internal VariablesContainer GetVariables(IBindingContext context)
        {
            DataBindingContext ctx = (DataBindingContext)context;
            lock (ctx)
            {
                if (ctx.Variables == null)
                {
                    //InvokeSchema schema = new InvokeSchema(Commands.CommandId.GetVariablesId);
                    //schema.Args.Add(new InvokeArg(KnownInvokeArgs.ContextId, ctx.Id));
                    //using (var cmd = CommandDispatcher.Dispatch(schema))
                    //{
                    //    InvokeResultSchema res = cmd.Invoke();
                    //    if (res.ContentType == KnownContentTypes.Variables)
                    //    {
                    //        VariablesRoot list = VariablesRoot.FromXml(res.Content);
                    //        ctx.Variables = new VariablesContainer(list.Vars);
                    //    }
                    //}

                    // clone default variables
                    var root = new VariablesRoot(this.DefaultVariables.Variables);
                    ctx.Variables = new VariablesContainer(root.Clone().Vars);
                }

                return ctx.Variables;
            }
        }

        internal string GetHashableValue()
        {
            string result = "";
            foreach (string contextId in m_Contexts.Keys)
            {
                result += String.Format("contextId=[{0}][", contextId);
                VariablesContainer vars = this.GetVariables(contextId);
                foreach (KeyValuePair<string, BrickVariable> kvp in vars)
                {
                    result += String.Format("{0} {1}={2}", kvp.Key, kvp.Value.Name, kvp.Value.CurrentValue);
                }
                result += "]";
            }
            return result;
        }

        /// <summary>
        /// Получить хэш статуса (SHA1).
        /// </summary>
        /// <param name="contextId"></param>
        /// <returns></returns>
        internal string GetStatusHash(string contextId)
        {
            string input;
            // формируем строку для вычисления хэша
            input = String.Format("contextId={0} [", contextId);
            // добавляем пары имя переменной - значение.
            VariablesContainer vars = this.GetVariables(contextId);
            foreach (KeyValuePair<string, BrickVariable> kvp in vars)
            {
                input += String.Format("{0}={1} ", kvp.Value.Name, kvp.Value.CurrentValue);
            }
            input += "]";
            // добавляем менеджеры olap
            DataBindingContext context = (DataBindingContext)this.GetContext(contextId);
            foreach (var item in context.DataManagers)
            {
                if (item is OlapDataManager)
                {
                    OlapDataManager manager = (OlapDataManager)item;
                    input += String.Format(" {0}={1}", manager.Name, manager.History.Size);
                }
            }
            // вычисляем хэш
            System.Security.Cryptography.SHA1Managed sha1 = new System.Security.Cryptography.SHA1Managed();
            string hash = BitConverter.ToString(sha1.ComputeHash(System.Text.Encoding.Unicode.GetBytes(input))).Replace("-", "");

            return hash;
        }

        public IServiceProvider Services { get; private set; }

        private DataAccessMode m_AccessMode = DataAccessMode.Default;
        public DataAccessMode AccessMode 
        {
            get
            {
                return m_AccessMode;
            }
            set
            {
                m_AccessMode = value;
                if (m_AccessMode == DataAccessMode.Immediate)
                {
                    ((Page)App.CurrentApp.Root.RootControl).RunImmediate.Visibility = Visibility.Collapsed;
                }
            }
        }

        public void Refresh(IBindingContext context)
        {
            if (this.AccessMode == DataAccessMode.Immediate)
            {
                this.RefreshImmediate(context);
            }
        }

        public void Refresh(IBrickContainer container, IBindingContext context)
        {
            if (this.AccessMode == DataAccessMode.Immediate)
            {
                foreach (var template in container.Templates)
                {
                    this.Refresh(template, context);
                }
                this.RefreshImmediate(container, context);
            }
        }

        public static DataBinder Current
        {
            get
            {
                return App.CurrentApp.Binder;
            }
        }

        DataSourceManager m_DataSources;
        public object CreateDataObject(string contextId, string dataSourceId)
        {
            var context = this.GetOrCreateContext(contextId) as DataBindingContext;
            if (context != null)
            {
                var bindingManager = context[dataSourceId];
                if (bindingManager != null)
                {
                    return bindingManager.DataManager[context];
                }
            }

            return null;
        }

        internal void RefreshImmediate(IBindingContext context)
        {
            var manager = context.Services.GetService<IUserTaskManager>();
            if (manager != null)
            {
                FormView form = App.CurrentApp.Root.AppManager.CurrentForm as FormView;
                if (form != null)
                {
                    manager.Dispatcher.BeginInvoke(() =>
                    {
                        this.RefreshImmediateCore(
                            form.Container,
                            context,
                            form.ContextWindows);
                    });
                }
            }
        }

        private void RefreshImmediateCore(
            IBrickContainer container, 
            IBindingContext context,
            IEnumerable<FormView> contextWindows)
        {
            if (container != null)
            {
                this.RefreshImmediate(container, context);
                foreach (var template in container.Templates)
                {
                    this.RefreshImmediateCore(template, context, null);
                }
                if (contextWindows != null)
                {
                    foreach (FormView fw in contextWindows)
                    {
                        this.RefreshImmediate(fw.Container, context);
                    }
                }
            }
        }

        internal void RefreshImmediate(IBrickContainer container, IBindingContext context)
        {
            var taskManager = context.Services.GetService<IUserTaskManager>();
            if (taskManager != null)
            {
                taskManager.RunTaskAsync(
                    string.Format(Localization.AppTask_RefreshingData, container.Title, container.Name),
                    state => this.RefreshCore((RefreshArgs)state),
                    new RefreshArgs(container, context, taskManager));
            }
        }

        private class RefreshArgs
        {
            public RefreshArgs(IBrickContainer container, IBindingContext context, IUserTaskManager taskManager)
            {
                this.Container = container;
                this.Context = context;
                this.TaskManager = taskManager;
                this.Names = new Dictionary<IBindableObject, string>();
                foreach (IBindableObject bo in container.Controls)
                {
                    FrameworkElement fe = bo as FrameworkElement;
                    if (fe != null)
                    {
                        this.Names.Add(bo, fe.Name);
                    }
                }
            }

            public readonly IBrickContainer Container;
            public readonly IBindingContext Context;
            public readonly IUserTaskManager TaskManager;
            public readonly IDictionary<IBindableObject, string> Names;
        }

        private void RefreshCore(object state)
        {
            RefreshArgs args = (RefreshArgs)state;
            foreach (IBindableObject root in args.Container.Controls)
            {
                foreach (DataBindingManagerHolder holder in root.Bindings.Values)
                {
                    try
                    {
                        holder.PreloadData();
                    }
                    catch (AdomdResponseException adomdExc)
                    {
                        // Error loading data source '{0}' on control '{1}'.
                        //
                        string controlName = "Unknown";
                        args.Names.TryGetValue(root, out controlName);

                        throw new AdomdResponseException(
                                string.Format(Localization.Exc_LoadingDataSourceOnControlExcption,
                                    holder.DataManager.Name, controlName) 
                                    + " " 
                                    + adomdExc.Message);
                    }
                    catch (Exception exc)
                    {
                        // Error loading data source '{0}' on control '{1}'.
                        //
                        string controlName = "Unknown";
                        args.Names.TryGetValue(root, out controlName);
                        throw new LoadingDataSourceOnControlException(
                            string.Format(Localization.Exc_LoadingDataSourceOnControlExcption,
                                holder.DataManager.Name, controlName),
                            exc);
                    }
                }
            }

            args.TaskManager.Dispatcher.BeginInvoke(() =>
            {
                if (args != null)
                {
                    foreach (IBindableObject root in args.Container.Controls)
                    {
                        // Если контекст равен null, значит контрол не был
                        // корректно забинден на данные. Обрабатывать нечего.
                        if (root.Context == null) continue;

                        FrameworkElement fe = root as FrameworkElement;
                        if (root != null && fe != null)
                        {
                            try
                            {
                                if (fe is IEditableObject)
                                {
                                    ((IEditableObject)fe).BeginEdit();
                                }

                                Type objType = root.GetType();
                                foreach (KeyValuePair<string, IBindingManager> kv in root.Bindings)
                                {
                                    PropertyInfo pi = objType.GetProperty(kv.Key, BindingFlags.Instance | BindingFlags.Public);
                                    if (pi != null)
                                    {
                                        DataBindingManagerHolder mgr = (DataBindingManagerHolder)kv.Value;
                                        this.SetBindingPropertyValue(
                                            new DataBindingEntry(mgr, pi, mgr.Path),
                                            fe,
                                            args.Context,
                                            true);
                                    }
                                }

                                if (fe is IEditableObject)
                                {
                                    ((IEditableObject)fe).EndEdit();
                                }
                            }
                            catch (Exception exc)
                            {
                                IBindingContext context = args.Context;
                                IOutputService output = (IOutputService)context.Services.GetService(typeof(IOutputService));
                                if (output != null)
                                {
                                    output.WriteException(exc);
                                }
                            }
                        }
                    }
                }
            });
        }

        private void SetBindingPropertyValue(
            DataBindingEntry entry, 
            FrameworkElement fe, 
            IBindingContext context,
            bool reEnterOnTimeout)
        {
            try
            {
                entry.SetValue(fe);
            }
            catch (TimeoutException timeoutExc)
            {
                if (reEnterOnTimeout)
                {
                    this.SetBindingPropertyValue(entry, fe, context, false);
                }
                else
                {
                    var output = context.Services.GetService<IOutputService>();
                    if (output != null)
                    {
                        output.WriteException(timeoutExc);
                    }
                }
            }
            catch (Exception exc)
            {
                var output = context.Services.GetService<IOutputService>();
                if (output != null)
                {
                    output.WriteException(
                        new DataBindingException(
                                string.Format(
                                    Localization.Exc_BindPropertyException, 
                                    entry.Property.Name, 
                                    fe.Name, 
                                    entry.Manager.DataManager.Name, 
                                    entry.Path),
                                exc));
                }
            }
        }

        NamedObjectCache m_NamedObjects;
        public BrickObject GetNamedObject(string name)
        {
            if (m_NamedObjects != null)
            {
                return m_NamedObjects[name];
            }

            return null;
        }

        public UserSettings UserSettings { get; internal set; }

        public void InitializeSettings()
        {
            this.InitializeSettings(this.UserSettings);
        }

        public void InitializeSettings(UserSettings settings)
        {
            if (settings != null)
            {
                this.UserSettings.Settings = settings.Settings;
                this.MergeVariables(settings.Variables);
            }
        }

        private void MergeVariables(IEnumerable<BrickVariable> vars)
        {
            foreach (BrickVariable var in vars)
            {
                this.DefaultVariables[var.Name] = var;
            }
            this.UserSettings.Variables.Clear();
            this.UserSettings.Variables.AddRange(this.DefaultVariables.Variables);
        }

        private MetaContainer m_MetaData;
        private BrickDacContainer m_DacContainer;
        public void InitializeMatadata(MetaContainer metadata)
        {
            m_MetaData = metadata;
            this.MergeVariables(metadata.Variables);
        }

        public void InitializeDac(BrickDacContainer dacContainer)
        {
            m_DacContainer = dacContainer;
            m_NamedObjects = new NamedObjectCache(dacContainer);
            m_DataSources = new DataSourceManager(dacContainer.DataSources);
            ((DataBindingContext)this.DefaultContext).Variables = new VariablesContainer(dacContainer.Variables);
        }

        public IEnumerable<string> GetContextWindows(string formId)
        {
            if (string.IsNullOrEmpty(formId))
            {
                throw new ArgumentNullException("formId");
            }
            foreach (FormToContextWindowBind bind in m_MetaData.ContextWindows)
            {
                if (bind.FormId == formId)
                {
                    return bind.ContextWindowIds;
                }
            }

            return new string[] { };
        }

        public void Initialize(IPrincipal principal)
        {
            try
            {
                InitializeInternal(typeof(IFormNavigator), Consts.NAVMANGER_CLASS_NAME);
                InitializeInternal(typeof(IScriptEngine), Consts.SCRIPTENGINE_CLASS_NAME);
                InitializeInternal(typeof(IExecutionContext), Consts.VARMANAGER_CLASS_NAME);
            }
            catch (Exception exc)
            {
                IOutputService output = App.CurrentApp.GetService<IOutputService>();
                if (output != null)
                {
                    output.WriteException(exc);
                }
            }
        }

        private IBindingContext GetOrCreateContext(string id)
        {
            IBindingContext context;
            m_Contexts.TryGetValue(id, out context);
            if (context == null)
            {
                context = new DataBindingContext(this, id);
                m_Contexts.Add(id, context);
            }

            return context;
        }

        private void InitializeInternal(Type srvType, string className)
        {
            object srv = App.CurrentApp.GetService(srvType);
            if (srv != null)
            {
                Type type = ClientResources.ClientAssembly.GetType(
                    string.Format("{0}.{1}", Consts.GLOBAL_NAMESPACE, className));
                if (type != null)
                {
                    MethodInfo init = type.GetMethod(Consts.INITIALIZE_METHOD_NAME, BindingFlags.Public | BindingFlags.Static);
                    if (init != null)
                    {
                        init.Invoke(null, new object[] { srv });
                    }
                }
            }
        }

        public IBindingManager GetBindingManager(string contextId, string dataSourceId)
        {
            var context = this.GetContext(contextId) as DataBindingContext;
            if (context != null)
            {
                return context[dataSourceId];
            }

            return null;
        }

        public string GetContentType(string dataSourceId)
        {
            return m_DataSources.GetContentType(dataSourceId);
        }

        public void Bind(
            FormView root, 
            IBrickContainer container,
            IDictionary<string, IDataManager> dataManagerCache)
        {
            if (container.DataBindings == null)
            {
                return;
            }

            IBindingContext context = this.GetOrCreateContext(root.Id);
            BindInternal(root, container, context, dataManagerCache);
        }


        internal void BindInternal(
            FrameworkElement control,
            IBrickContainer container,
            IBindingContext defaultContext,
            IDictionary<string, IDataManager> dataManagerCache)
        {
            int childCount = VisualTreeHelper.GetChildrenCount(control);
            if (control is ScrollViewer && childCount == 0)
            {
                control = ((ScrollViewer)control).Content as FrameworkElement;
                childCount = VisualTreeHelper.GetChildrenCount(control);
            }
            for (int i = 0; i < childCount; i++)
            {
                FrameworkElement fe = VisualTreeHelper.GetChild(control, i) as FrameworkElement;
                if (fe == null) continue;

                IBindingContext context = container.DataBindingContext.ContainsKey(fe.Name) ?
                    this.GetOrCreateContext(container.DataBindingContext[fe.Name]) :
                    defaultContext;
                if (fe is IBindableObject)
                {
                    ((IBindableObject)fe).Context = context;
                }

                List<BrickDataBinding> bindings;
                container.DataBindings.TryGetValue(fe.Name, out bindings);
                IBindableObject bo = fe as IBindableObject;
                if (bo != null && bindings != null && bindings.Count > 0)
                {
                    foreach (BrickDataBinding binding in bindings)
                    {
                        bo.Bindings.Add(
                            binding.PropertyName,
                            GetBindingManager(context, binding, dataManagerCache));
                    }
                }

                BindInternal(fe, container, defaultContext, dataManagerCache);
            }
        }

        private IBindingManager GetBindingManager(
            IBindingContext context, 
            BrickDataBinding binding,
            IDictionary<string, IDataManager> dataManagerCache)
        {
            IDataManager cdm;
            dataManagerCache.TryGetValue(binding.DataSourceName, out cdm);
            if (cdm == null)
            {
                cdm = m_DataSources.CreateDataManager(binding.DataSourceName);
                if (cdm == null)
                {
                    throw new Exception(string.Format("Cannot create datasource '{0}'"));
                }

                dataManagerCache.Add(binding.DataSourceName, cdm);
            }

            return new DataBindingManagerHolder(context[cdm], binding.Path);
        }


        public IBindingContext GetContext(FrameworkElement root)
        {
            int childCount = VisualTreeHelper.GetChildrenCount(root);
            if (root is ScrollViewer && childCount == 0)
            {
                root = ((ScrollViewer)root).Content as FrameworkElement;
                childCount = VisualTreeHelper.GetChildrenCount(root);
            }
            for (int i = 0; i < childCount; i++)
            {
                FrameworkElement fe = VisualTreeHelper.GetChild(root, i) as FrameworkElement;
                if (fe == null) continue;

                IBindableObject bo = fe as IBindableObject;
                if (bo != null)
                {
                    return bo.Context;
                }

                IBindingContext context = this.GetContext(fe);
                if (context != null)
                {
                    return context;
                }
            }

            return null;
        }

        public void DestroyContext(string id)
        {
            m_Contexts.Remove(id);
        }

        public void Reset(IBindingContext context)
        {
            var ctx = context as DataBindingContext;
            if (ctx != null)
            {
                ctx.Variables = null;
                this.ResetOlapManagers(context);
            }
        }

        public void ResetOlapManagers(IBindingContext context)
        {
            var ctx = context as DataBindingContext;
            if (ctx != null)
            {
                ctx.ResetOlapManagers();
            }
        }

    }
    /*
        #region ClientDataManager
        private class ClientDataManager : IDataManager
        {
            public static readonly ClientDataManager Empty = new ClientDataManager();
            public bool IsEmpty
            {
                get
                {
                    return this.Equals(Empty);
                }
            }
            private ClientDataManager()
            {
            }

            public ClientDataManager(string dataSourceId)
            {
                this.DataSourceId = dataSourceId;
            }

            public string DataSourceId { get; private set; }

            #region IDataManager Members

            public object this[IBindingContext context]
            {
                get
                {
                    InvokeSchema schema = new InvokeSchema(Commands.CommandId.GetDataCommandId);
                    schema.Args.Add(new InvokeArg(KnownInvokeArgs.ContextId, ((DataBindingContext)context).Id));
                    schema.Args.Add(new InvokeArg(KnownInvokeArgs.DataSourceId, this.DataSourceId));

                    using (var cmd = CommandDispatcher.Dispatch(schema))
                    {
                        InvokeResultSchema result = cmd.Invoke();

                        if (result.ContentType == KnownContentTypes.RelationalData)
                        {
                            return DataTableWrapper.Parse(result.Content);
                        }
                        if (result.ContentType == KnownContentTypes.MultidimData)
                        {
                            return CellSetData.Parse(result.Content);
                        }

                        return null;
                    }
                }
            }

            public object this[string name]
            {
                get
                {
                    throw new NotImplementedException();
                }
                set
                {
                    throw new NotImplementedException();
                }
            }

            public bool Cancel()
            {
                return false;
            }

            public string Name
            {
                get
                {
                    return this.DataSourceId;
                }
            }

            #endregion
        }
        #endregion
     */
}
